/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 * 
 * U.S. Government Rights - Commercial software. Government users are subject to
 * the Sun Microsystems, Inc. standard license agreement and applicable
 * provisions of the FAR and its supplements.
 * 
 * 
 * This distribution may include materials developed by third parties. Sun, Sun
 * Microsystems, the Sun logo and Solaris are trademarks or registered
 * trademarks of Sun Microsystems, Inc. in the U.S. and other countries.
 */


/*
 * Note: this file originally auto-generated by mib2c using :
 * mib2c.iterate.conf,v 1.1.1.1 2003/03/26 18:12:29 pcarroll Exp $
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#include <net-snmp/agent/net-snmp-agent-includes.h>
#include "demo_module_5.h"
#include <net-snmp/agent/agent_trap.h>

/*
 * MAXNAMELEN is the maximum permissible file name defined in param.h 
 */

fileEntry      *fileList = 0;
char            file1[MAXNAMELEN], file2[MAXNAMELEN], file3[MAXNAMELEN],
                file4[MAXNAMELEN];



/** Initialize the me5FileTable table by defining its contents and how it's structured */

void
initialize_table_me5FileTable(void)
{
    static oid      me5FileTable_oid[] = {1, 3, 6, 1, 4, 1, 42, 2, 2, 4, 4, 5, 2, 1};
    netsnmp_table_registration_info *table_info;
    netsnmp_handler_registration *my_handler;
    netsnmp_iterator_info *iinfo;

    /* create the table structure itself */
    table_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info);
    iinfo = SNMP_MALLOC_TYPEDEF(netsnmp_iterator_info);

    /*
     * if your table is read only, it's easiest to change the
     * HANDLER_CAN_RWRITE definition below to HANDLER_CAN_RONLY
     */
    my_handler = netsnmp_create_handler_registration("me5FileTable",
						     me5FileTable_handler,
						     me5FileTable_oid,
					       OID_LENGTH(me5FileTable_oid),
						     HANDLER_CAN_RWRITE);

    if (!my_handler || !table_info || !iinfo) {
	return;			/* mallocs failed */
    }

    /***************************************************
     * Setting up the table's definition
     */

    netsnmp_table_helper_add_indexes(table_info,
				     ASN_UNSIGNED,	/* index: me5FileIndex */
				     0);

    table_info->min_column = 1;
    table_info->max_column = 4;

    /* iterator access routines */
    iinfo->get_first_data_point = me5FileTable_get_first_data_point;
    iinfo->get_next_data_point = me5FileTable_get_next_data_point;

    iinfo->table_reginfo = table_info;

    /***************************************************
     * registering the table with the master agent
     */

    DEBUGMSGTL(("initialize_table_me5FileTable",
		"Registering table me5FileTable as a table iterator\n"));
    netsnmp_register_table_iterator(my_handler, iinfo);
}


/** Initializes the demo_module_5 module */


void
init_demo_module_5(void)
{

    /* here we initialize all the tables we're planning on supporting */


    initialize_table_me5FileTable();


    /*
     * These are the default files that are monitored by the module if there
     * is no persistent data (file names to be monitored). This is likely
     * during the first running on the module, when the .conf file does not
     * have any file related information.
     */

    strcpy(file1, "/etc/hosts");
    strcpy(file2, "/etc/group");
    strcpy(file3, "/etc/passwd");
    strcpy(file4, "/etc/system");


    /*
     * Register for tokens from demo_module_5.conf file. The names of the 
     * tokens are demo5_file1,demo5_file2,demo5_file3,demo5_file4. The function
     * demo5_load_tokens is called whenever these 4 tokens are encountered in
     * demo_module_5.conf file.
     */

    register_config_handler(DEMO5_CONF_FILE, "demo5_file1",
			    demo5_load_tokens, NULL, NULL);

    register_config_handler(DEMO5_CONF_FILE, "demo5_file2",
			    demo5_load_tokens, NULL, NULL);

    register_config_handler(DEMO5_CONF_FILE, "demo5_file3",
			    demo5_load_tokens, NULL, NULL);

    register_config_handler(DEMO5_CONF_FILE, "demo5_file4",
			    demo5_load_tokens, NULL, NULL);



    /*
     * Register for a callback when all the configuration files are read. The
     * callback function here is demo5_post_read_config
     */

    snmp_register_callback(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_POST_READ_CONFIG,
			   demo5_post_read_config, NULL);


}


/** returns the first data point within the me5FileTable table data.

    Set the my_loop_context variable to the first data point structure
    of your choice (from which you can find the next one).  This could
    be anything from the first node in a linked list, to an integer
    pointer containing the beginning of an array variable.

    Set the my_data_context variable to something to be returned to
    you later that will provide you with the data to return in a given
    row.  This could be the same pointer as what my_loop_context is
    set to, or something different.

    The put_index_data variable contains a list of snmp variable
    bindings, one for each index in your table.  Set the values of
    each appropriately according to the data matching the first row
    and return the put_index_data variable at the end of the function.
*/


netsnmp_variable_list *
me5FileTable_get_first_data_point(void **my_loop_context, void **my_data_context,
				  netsnmp_variable_list * put_index_data,
				  netsnmp_iterator_info * mydata)
{

    netsnmp_variable_list *vptr;

    fileEntry      *firstFile = fileList;
    if (!firstFile) {
	return NULL;
    }
    *my_loop_context = firstFile;
    *my_data_context = firstFile;


    vptr = put_index_data;

    snmp_set_var_value(vptr, (u_char *) & fileList->findex, sizeof(fileList->findex));
    vptr = vptr->next_variable;
    
    return put_index_data;
}


/** functionally the same as me5FileTable_get_first_data_point, but
   my_loop_context has already been set to a previous value and should
   be updated to the next in the list.  For example, if it was a
   linked list, you might want to cast it and the return
   my_loop_context->next.  The my_data_context pointer should be set
   to something you need later and the indexes in put_index_data
   updated again. */



netsnmp_variable_list *
me5FileTable_get_next_data_point(void **my_loop_context, void **my_data_context,
				 netsnmp_variable_list * put_index_data,
				 netsnmp_iterator_info * mydata)
{

    netsnmp_variable_list *vptr;
    fileEntry      *nextNode = (fileEntry *) * my_loop_context;
    nextNode = nextNode->next;

    if (!nextNode) {
	return NULL;
    }
    *my_loop_context = nextNode;
    *my_data_context = nextNode;

    vptr = put_index_data;


    
    snmp_set_var_value(vptr, (u_char *) & nextNode->findex,
	sizeof(nextNode->findex));
    vptr = vptr->next_variable;

    return put_index_data;
}


/** handles requests for the me5FileTable table, if anything else needs to be done */

int
me5FileTable_handler(
		     netsnmp_mib_handler * handler,
		     netsnmp_handler_registration * reginfo,
		     netsnmp_agent_request_info * reqinfo,
		     netsnmp_request_info * requests)
{

    netsnmp_request_info *request;
    netsnmp_table_request_info *table_info;
    netsnmp_variable_list *var;
    fileEntry      *data;
    char           *fileName = NULL;
    char           *undofn;
    int		   len;
		
    char            filebuf[255];

    for (request = requests; request; request = request->next) {

	var = request->requestvb;
	if (request->processed != 0)
	    continue;

	/*
	 * the following extracts the my_data_context pointer set in the loop
	 * functions above.  You can then use the results to help return data
	 * for the columns of the me5FileTable table in question
	 */

	data = (fileEntry *) netsnmp_extract_iterator_context(request);
	if (data == NULL) {
	    if (reqinfo->mode == MODE_GET) {
		netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHINSTANCE);
	    } else {
		netsnmp_set_request_error(reqinfo, request,
					  SNMP_ERR_NOCREATION);
	    }
	    continue;
	} else {
	    struct stat     fAttrib;
	    if (stat(GetFileName(data->findex), &fAttrib) != -1) {
		data->fileSize = fAttrib.st_size;
		sprintf(data->filePerm, "%o", fAttrib.st_mode & 0777);
	    } else {
		data->fileSize = 0;
		sprintf(data->filePerm, "%d", -1);
	    }

	}

	/* extracts the information about the table from the request */
	table_info = netsnmp_extract_table_info(request);
	/* table_info->colnum contains the column number requested */
	/*
	 * table_info->indexes contains a linked list of snmp variable
	 * bindings for the indexes of the table.  Values in the list have
	 * been set corresponding to the indexes of the request
	 */

	if (table_info == NULL) {
	    continue;
		}
	switch (reqinfo->mode) {
	    /*
	     * the table_iterator helper should change all GETNEXTs into GETs
	     * for you automatically, so you don't have to worry about the
	     * GETNEXT case.  Only GETs and SETs need to be dealt with here
	     */
	case MODE_GET:
	    switch (table_info->colnum) {
	    case COLUMN_ME5FILEINDEX:
			snmp_set_var_typed_value(var, ASN_UNSIGNED, 
			(u_char *) & data->findex,
		sizeof(data->findex));	
		break;	

	    case COLUMN_ME5FILENAME:
			snmp_set_var_typed_value(var, ASN_OCTET_STR, 
			(u_char *) data->fileName,
	strlen(data->fileName));
		break;

	    case COLUMN_ME5FILESIZE:
			snmp_set_var_typed_value(var, ASN_UNSIGNED, 
			(u_char *) & data->fileSize,
		sizeof(data->fileSize));
		break;

	    case COLUMN_ME5FILEPERM:
		snmp_set_var_typed_value(var, ASN_OCTET_STR, (u_char *) data->filePerm,
		strlen(data->filePerm));
		break;

	    default:
		/* We shouldn't get here */
		snmp_log(LOG_ERR, 
			"problem encountered in me5FileTable_handler: unknown column\n");
	    }
	    break;

	case MODE_SET_RESERVE1:
	    /* set handling... */
	    switch (table_info->colnum) {
		/*
		 * Check that the value being set is acceptable
		 */
	    case COLUMN_ME5FILENAME:
		if (var->type != ASN_OCTET_STR) {
		    DEBUGMSGTL(("me5FileTable", "COLUMN NAME\n"));
		    DEBUGMSGTL(("me5FileTable", "%x not octet string type", var->type));
		    netsnmp_set_request_error(reqinfo, request, SNMP_ERR_WRONGTYPE);
		    return SNMP_ERR_WRONGTYPE;
		}
		if (!var->val.string) {
		    DEBUGMSGTL(("me2FileTable", "Empty file name"));
		    netsnmp_set_request_error(reqinfo, request, SNMP_ERR_WRONGVALUE);
		    return SNMP_ERR_WRONGVALUE;
		}
		break;
	    default:
		/* We shouldn't get here */
		snmp_log(LOG_ERR, 
			"problem encountered in me5FileTable_handler: unknown column\n");
		netsnmp_set_request_error(reqinfo, request, SNMP_ERR_READONLY);
		return SNMP_ERR_NOTWRITABLE;
	    }
	    break;
	case MODE_SET_RESERVE2:
	    /*
	     * This is conventially where any necesary resources are
	     * allocated (e.g. calls to malloc)
	     */

	    /* Store old info for undo later  */

	    undofn = GetFileName(data->findex);
	    if (undofn) {
		if (!(fileName = strdup(undofn))) {
		    netsnmp_set_request_error(reqinfo, request,
					      SNMP_ERR_RESOURCEUNAVAILABLE);
		} else
		    netsnmp_request_add_list_data(request,
						  netsnmp_create_data_list
					    (ME5FILE_SET_FILENAME, fileName,
					     free));

	    }
	    break;
	case MODE_SET_FREE:

	    /*
	     * This is where any of the above resources are freed again
	     * (because one of the other values being SET failed for some
	     * reason).
	     */

	    /*
	     * The netsnmp_free_list_data should take care of the alocated
	     * resources
	     */

	    break;
	case MODE_SET_ACTION:
	    /*
	     * Set the variable as requested. Note that this may need to be
	     * reversed, so save any information needed to do this.
	     */
	  len =  var->val_len;
	  var->val.string[len] = '\0';
	  if (!ChangeItem(data->findex, (char *) var->val.string )) {
	    netsnmp_set_request_error(reqinfo, request,
				      SNMP_ERR_COMMITFAILED);
	  }
	  break;
	  
	case MODE_SET_COMMIT:
	    /*
	     * Everything worked, so we can discard any saved information,
	     * and make the change permanent (e.g. write to the config file).
	     * We also free any allocated resources.
	     * 
	     */

	    /* Persist the file information */

		snprintf(&filebuf[0], MAXNAMELEN, "demo5_file%d %s",
		    data->findex, data->fileName);


		
		read_config_store(DEMO5_CONF_FILE, &filebuf[0]);

	    /*
	     * The netsnmp_free_list_data should take care of the alocated
	     * resources
	     */


	    break;
	case MODE_SET_UNDO:
	    /*
	     * Something failed, so re-set the variable to its previous value
	     * (and free any allocated resources).
	     */

	    if (GetFileName(data->findex)) {
		/*******  Get the saved value ************/
		undofn = (char *) netsnmp_request_get_list_data(request,
						      ME5FILE_SET_FILENAME);
		if (!ChangeItem(data->findex, undofn)) {
		    netsnmp_set_request_error(reqinfo, request,
					      SNMP_ERR_UNDOFAILED);
		}
	    }
	    break;

	default:
	    snmp_log(LOG_ERR, 
		"problem encountered in me5FileTable_handler: unsupported mode\n");
	}
    }

    return SNMP_ERR_NOERROR;
}

/* Function to add a fileName to the linked list of files */

int 
AddItem(char *fileName)
{

    fileEntry      *fprt = fileList;
    struct stat     fAttrib;	/* Need to check if memory is valid */
    if (!fileName || !strlen(fileName)) {
	return FALSE;
    }
    if (stat(fileName, &fAttrib) == -1) {
	/*
	 * Unable to get the file information, it could be more than file not
	 * exists if (errno == ENOENT) { return FALSE; } return FALSE;
	 */
	DEBUGMSGTL(("demo_module_5", "Can't access the file %s", fileName));
    }
    if (fprt != NULL) {
	while (fprt->next != NULL) {
	    fprt = fprt->next;
	}
	fprt->next = (fileEntry *) malloc(sizeof(fileEntry));
	fprt->next->findex = fprt->findex + 1;
	fprt = fprt->next;
	fprt->next = NULL;
	strcpy(fprt->fileName, fileName);
	fprt->fileSize = fAttrib.st_size;
	sprintf(fprt->filePerm, "%d", fAttrib.st_mode);
    } else {
	fprt = (fileEntry *) malloc(sizeof(fileEntry));
	fprt->next = NULL;
	fprt->findex = 1;
	strcpy(fprt->fileName, fileName);
	fprt->fileSize = fAttrib.st_size;
	sprintf(fprt->filePerm, "%d", fAttrib.st_mode);
	fileList = fprt;
    }

    return TRUE;
}

/*
 * Function to change the file for a particular index. This function is
 * called when a snmp set request arrives to change the list of files being
 * monitored.
 */

int 
ChangeItem(int fileIndex, char *fileName)
{

    fileEntry      *tempp = fileList;

    if (!fileName || !strlen(fileName)) {
	return FALSE;
    }

       while (tempp != NULL) {
	if (tempp->findex == fileIndex) {
	    strcpy(tempp->fileName, fileName);
	    switch(fileIndex) {
	    case 1:
	      strcpy(file1, fileName);
	    case 2:
	      strcpy(file2, fileName);
	    case 3:
	      strcpy(file3, fileName);
	    case 4:
	      strcpy(file4, fileName);
	    }
	      return TRUE;
	    
	}
	    tempp = tempp->next;
	}
	
	return FALSE;
}

/* Function to return the filename corresponding to an index */

char           *
GetFileName(int fIndex)
{
    fileEntry      *fprt = fileList;
    while (fprt != NULL) {
	if (fprt->findex == fIndex) {
	    return fprt->fileName;
	}
	fprt = fprt->next;
    }
    return NULL;

}

/*
 * Function that is called whenever interested tokens are encountered in
 * demo_module_5.conf file. The token values represent the persistent filename
 * information.
 */

void 
demo5_load_tokens(const char *token, char *cptr)
{

    if (strcmp(token, "demo5_file1") == 0) {
	strcpy(file1, cptr);
    } else if (strcmp(token, "demo5_file2") == 0) {
	strcpy(file2, cptr);
    } else if (strcmp(token, "demo5_file3") == 0) {
	strcpy(file3, cptr);
    } else if (strcmp(token, "demo5_file4") == 0) {
	strcpy(file4, cptr);
    } else {
	/* Do Nothing */
    }

    return;

}

/*
 * Function that persists file information. This is called by the agent
 * whenever data needs to be persisted.
 */

int 
demo5_persist_data(int a, int b, void *c, void *d)
{

    char            filebuf[300];


    sprintf(filebuf, "demo5_file1 %s", file1);
    read_config_store(DEMO5_CONF_FILE, filebuf);


    sprintf(filebuf, "demo5_file2 %s", file2);
    read_config_store(DEMO5_CONF_FILE, filebuf);


    sprintf(filebuf, "demo5_file3 %s", file3);
    read_config_store(DEMO5_CONF_FILE, filebuf);


    sprintf(filebuf, "demo5_file4 %s", file4);
    read_config_store(DEMO5_CONF_FILE, filebuf);

}

/*
 * Callback function that is called after all the configuration files are
 * read by the agent. See init_demo_module_5 function to see how this
 * callback is specified.
 * 
 * When this function is called, any persistent file information would have been
 * read into the module. These files are added to the file list.
 * 
 * The callback function to persist data (demo5_persist_data) is registered.
 */

int 
demo5_post_read_config(int a, int b, void *c, void *d)
{

    if (!AddItem(file1))
	snmp_log(LOG_ERR, "Failed to add instance in init_demo_module_5\n");
    if (!AddItem(file2))
	snmp_log(LOG_ERR, "Failed to add instance in init_demo_module_5\n");
    if (!AddItem(file3))
	snmp_log(LOG_ERR, "Failed to add instance in init_demo_module_5\n");
    if (!AddItem(file4))
	snmp_log(LOG_ERR, "Failed to add instance in init_demo_module_5\n");


    snmp_register_callback(SNMP_CALLBACK_LIBRARY, SNMP_CALLBACK_STORE_DATA,
			   demo5_persist_data, NULL);


}

